package de.teamskill.pux.engine;

/**
 * OutputPort
 * 
 * @author <a href="mailto:jfendler@polytechnic.edu.na">Jens Fendler</a>
 * 
 */
public class OutputPort extends Port implements Logging {

	/**
	 * OutputPort Constructor
	 * 
	 * @param name
	 */
	public OutputPort(String name) {
		this(name, null);
	}

	/**
	 * OutputPort Constructor
	 * 
	 * @param name
	 * @param node
	 */
	public OutputPort(String name, AbstractNode node) {
		this(name, node, null);
	}

	public OutputPort(String name, AbstractNode node, Class<?> type) {
		super(name, node, type);
	}

	/**
	 * Pushes the DataElement contained in the output buffer into the target
	 * port's input buffer. This method automatically clears the output buffer.
	 * 
	 */
	public void push() {
		push(true, false);
	}

	/**
	 * Convenience method to put a DataElement into the output buffer, and to
	 * push the data into the target port's input buffer. The output buffer is
	 * cleared automatically.
	 * 
	 * @param data
	 */
	public void pushBuffer(DataElement data) {
		pushBuffer(data, true, false);
	}

	/**
	 * Convenience method to set the output buffer and push it immediately.
	 * Optionally clears the output buffer
	 * 
	 * @param data
	 * @param clearOutputBuffer
	 * @param overwriteTargetBuffer
	 */
	public void pushBuffer(DataElement data, boolean clearOutputBuffer, boolean overwriteTargetBuffer) {
		setBuffer(data);
		push(clearOutputBuffer, overwriteTargetBuffer);
	}

	/**
	 * Push the DataElement contained in the output buffer into the target
	 * port's input buffer. Optionally clears the output buffer. Furthermore,
	 * this method can wait until the input buffer of the remote port becomes
	 * empty before overwriting any data.
	 * 
	 * @param clearOutputBuffer
	 * @param overwriteTargetBuffer
	 */
	public void push(final boolean clearOutputBuffer, final boolean overwriteTargetBuffer) {
		DataElement data = getBuffer();
		LOG_PORT.finer(OutputPort.this.getName() + " pushing value into remote port");

		// check if there is another node's port connected to this
		// output port
		InputPort remoteInputPort = (InputPort) getRemotePort();
		if (remoteInputPort != null) {
			if (overwriteTargetBuffer || !remoteInputPort.hasData()) {
				remoteInputPort.setBuffer(data);
			} else {
				while (remoteInputPort.hasData()) {
					// wait for remote port to become empty
					try {
						Thread.sleep(DATA_PUSH_WAIT_DELAY);
					} catch (InterruptedException e) {
						LOG_PORT.warning(OutputPort.this.getName()
								+ " interrupted while waiting for remote port to be freed: "
								+ e.getLocalizedMessage());
					}
				}
				remoteInputPort.setBuffer(data);
			}

			// start the remote port's node if it is not already running
			AbstractNode remoteNode = remoteInputPort.getNode();
			if (!remoteNode.isRunning()) {
				remoteNode.startNode();
			}
		}

		// clear the output buffer
		if (clearOutputBuffer) {
			setBuffer(null);
		}
	}

	/**
	 * Convenience method to wrap a data object in a DataElement and to push it
	 * immediately, clearing the output buffer automatically.
	 * 
	 * @param bi
	 */
	public void pushData(Object dataObject) {
		pushBuffer(new DataElement(dataObject));
	}

	/**
	 * Convenience method to wrap a data object in a DataElement and to push it
	 * immediately. The output buffer is optionally cleared. Data in the remote
	 * input buffer is overwritten.
	 * 
	 * @param dataObject
	 * @param clearOutputBuffer
	 */
	public void pushData(Object dataObject, boolean clearOutputBuffer) {
		pushBuffer(new DataElement(dataObject), clearOutputBuffer, true);
	}

	/**
	 * pushData
	 * 
	 * @param dataObject
	 * @param clearOutputBuffer
	 * @param overwriteTargetBuffer
	 */
	public void pushData(Object dataObject, boolean clearOutputBuffer, boolean overwriteTargetBuffer) {
		pushBuffer(new DataElement(dataObject), clearOutputBuffer, overwriteTargetBuffer);
	}

}
